
from django.shortcuts import render,redirect,get_object_or_404
from django.http import JsonResponse,HttpResponse,HttpRequest,HttpResponseRedirect,HttpResponseBadRequest,HttpResponseNotFound,FileResponse,HttpResponseForbidden
from django.views.decorators.http import require_GET,require_POST
from django.views.decorators.cache import never_cache
from .models import User, Image,Tag,ImageTag
from .forms import ImageUploadForm
import hashlib
from django.db.models import OuterRef, Exists
from django_ratelimit.decorators import ratelimit

def api_ratelimit_exceed(request:HttpRequest):
    if login_check(request,True): return None
    return HttpResponse(content="Too many requests.",status=429)

def web_ratelimit_exceed(request:HttpRequest):
    if login_check(request,True): return None
    return render(request,"error.html",{'message':'Too many requests.'},status=429)

@ratelimit(key='ip',rate='8/m',block=False)
def user_profile(request, userid):
    if getattr(request,'limited',False):
        response=web_ratelimit_exceed(request)
        if response is not None:
            return response

    # 获取用户信息
    user_profile = get_object_or_404(User, id=userid)

    if user_profile.is_banned==True and login_check(request,True)==False:
        return render(request,"error.html",{'message':"The user is banned"})
    
    # 检查是否是查看自己的主页
    is_own_profile = False
    if login_check(request):  # 使用您的自定义登录检查函数
        # 从cookie中获取当前登录用户的ID
        logged_in_user_id = request.COOKIES.get("username")
        if logged_in_user_id and int(logged_in_user_id) == userid:
            is_own_profile = True
    
    # 获取用户上传的图片
    user_images = Image.objects.filter(uploader=user_profile, display=True).order_by('-date')
    
    # 分页处理
    paginator = Paginator(user_images, 12)
    page_number = request.GET.get('page', 1)
    page_obj = paginator.get_page(page_number)
    
    # 获取图片总数
    image_count = user_images.count()
    
    return render(request, 'profile.html', {
        'user_profile': user_profile,
        'user_images': page_obj,
        'image_count': image_count,
        'is_own_profile': is_own_profile,
    })

# Create your views here.
@ratelimit(key='ip',rate='8/m',block=False)
def register(request:HttpRequest):
    if getattr(request,'limited',False):
        response=web_ratelimit_exceed(request)
        if response is not None:
            return response
    if request.method=='POST':
        username=request.POST.get('username')
        password=request.POST.get('password')
        if User.objects.filter(username=username).exists():
            return render(request,"error.html",{'message':'Existing user<a href=\"/\">Back to home</a>'})
        if len(username)>64:
            return render(request,"error.html",{'message':'Invalid name.'})
        username=username.strip()
        if len(username)==0:
            return render(request,"error.html",{'message':'Invalid name.'})
        user=User()
        user.username=username
        user.set_password(password)
        user.full_clean()
        user.save()
        return render(request,"error.html",{'message':'Successfully registered'})
    return render(request,'register.html')

@ratelimit(key='ip',rate='8/m',block=False)
def login(request:HttpRequest):
    if getattr(request,'limited',False):
        response=web_ratelimit_exceed(request)
        if response is not None:
            return response
    if request.method=='POST':
        username=request.POST.get('username')
        password=request.POST.get('password')
        # print("LOGIN TRY ",username,password)
        if User.objects.filter(username=username).exists()==False:
            return render(request,"error.html",{"message":"User not found<a href=\"/\">Back to home</a>"})
        user=User.objects.filter(username=username).first() # should have exactly one obj
        if user.is_banned==True:
            return render(request,"error.html",{"message":"User is banned<a href=\"/\">Back to home</a>"})
        if user.check_password(password)==False:
            return render(request,"error.html",{"message":"Incorrect password<a href=\"/\">Back to home</a>"})
        token=user.generate_session_token()
        respose=render(request,"error.html",{'message':'Successfully logged in<script>window.location.href=\'/\'</script>'})
        respose.set_cookie("session_token",token)
        respose.set_cookie("username",user.id)
        return respose
    return render(request,"login.html")

@ratelimit(key='ip',rate='8/m',block=False)
def logout(request:HttpRequest):
    if getattr(request,'limited',False):
        response=web_ratelimit_exceed(request)
        if response is not None:
            return response
    username=request.COOKIES.get("username")
    if User.objects.filter(id=username).exists()==False:
        return render(request,"error.html",{"message":"User not found<a href=\"/\">Back to home</a>"})
    user=User.objects.filter(id=username).first()
    if user.session_token!="" and user.session_token!=request.COOKIES.get("session_token"):
        return render(request,"error.html",{"message":"You haven't logged in yet<a href=\"/\">Back to home</a>"})
    if user.session_token=="":
        return render(request,"error.html",{"message":"You haven't logged in yet<a href=\"/\">Back to home</a>"})
    user.clear_session_token()
    response=render(request,"error.html",{'message':'Successfully logged out<a href=\"/\">Back to home</a>'})
    response.delete_cookie("session_token")
    response.delete_cookie("username")
    return response

def login_check(request:HttpRequest,ensure_admin=False):
    username=request.COOKIES.get("username")
    if User.objects.filter(id=username).exists()==False:
        return False
    user=User.objects.filter(id=username).first()
    if user.session_token!="" and user.session_token!=request.COOKIES.get("session_token"):
        return False
    if user.session_token=="":
        return False
    if ensure_admin==True and user.is_admin==False:
        return False
    if user.is_banned:
        return False
    return True

@ratelimit(key='ip',rate='5/m',block=False)
@ratelimit(key='ip',rate='30/d',block=False)
def upload_image(request:HttpRequest):
    if getattr(request,'limited',False):
        response=web_ratelimit_exceed(request)
        if response is not None:
            return response
    username=request.COOKIES.get("username")
    if User.objects.filter(id=username).exists()==False:
        return render(request,"error.html",{"message":"User not found<a href=\"/\">Back to home</a>"})
    user=User.objects.filter(id=username).first()
    if user.session_token!="" and user.session_token!=request.COOKIES.get("session_token"):
        return render(request,"error.html",{"message":"You haven't logged in yet<a href=\"/\">Back to home</a>"})
    if user.session_token=="":
        return render(request,"error.html",{"message":"You haven't logged in yet<a href=\"/\">Back to home</a>"})
    if request.method=="POST":
        form=ImageUploadForm(request.POST,request.FILES)
        if form.is_valid():
            image=form.save(commit=False)
            image.uploader=user
            image.display=True
            image.save()
            return HttpResponseRedirect(f"/images/{image.id}")
        else:
            return render(request,"error.html",{"message":"Invalid upload<a href=\"/\">Back to home</a>"})
    form=ImageUploadForm()
    return render(request,"upload.html",{'form':form})

@ratelimit(key='ip',rate='20/m',block=False)
def view_image(request:HttpRequest,image_id):
    if getattr(request,'limited',False):
        response=web_ratelimit_exceed(request)
        if response is not None:
            return response
    try:
        if login_check(request,True):
            image=Image.objects.get(id=image_id)
        else:
            image=Image.objects.get(id=image_id,display=True)

        return render(request,"view.html",{'image':image})
    except Image.DoesNotExist:
        return render(request,"error.html",{"message":"Image not available<a href=\"/\">Back to home</a>"})

@ratelimit(key='ip',rate='50/m',block=False)
def index(request:HttpRequest):
    if getattr(request,'limited',False):
        response=web_ratelimit_exceed(request)
        if response is not None:
            return response
    return render(request,"index.html")

from django.core.paginator import Paginator,PageNotAnInteger,EmptyPage

@ratelimit(key='ip',rate='15/m',block=False)
def image_list(request):
    if getattr(request,'limited',False):
        response=web_ratelimit_exceed(request)
        if response is not None:
            return response
    if login_check(request,True):
        images = Image.objects.all().order_by('-date')
    else:
        images = Image.objects.filter(display=True).order_by('-date')
    
    # 分页处理
    paginator = Paginator(images, 12)  # 每页12张图片
    page_number = request.GET.get('page', 1)
    
    try:
        page_obj = paginator.get_page(page_number)
    except PageNotAnInteger:
        # 如果页码不是整数，返回第一页
        page_obj = paginator.page(1)
    except EmptyPage:
        # 如果页码超出范围，返回最后一页
        page_obj = paginator.page(paginator.num_pages)
    
    return render(request, 'list_tmp.html', {
        'images': page_obj,
        'paginator': paginator
    })
from django.db.models import Q
from django.utils import timezone
from datetime import datetime
import json

@ratelimit(key='ip',rate='20/m',block=False)
@require_GET
def get_all_tags(request):
    if getattr(request,'limited',False):
        response=api_ratelimit_exceed(request)
        if response is not None:
            return response
    tags=Tag.objects.all().values('name','tag_type','description')
    return JsonResponse(list(tags),safe=False)

def get_date(str:str):
    return timezone.make_aware(datetime.strptime(str, '%Y-%m-%d'))
def get_bool(s:str):
    if isinstance(s,str)==False:
        return s
    BOOL=['True','true','1','TRUE','yes','ok','YES','Yes','Ok','OK']
    return True if s in BOOL else False
class TagQueryFactory:
    AVAL_TYPE=['INT','BOOL','DATE','STR','FLOAT']
    FIELD_NAME={'INT':'int_value','BOOL':'bool_value','DATE':'date_value','STR':'str_value',"float":"float_value"}
    AVAL_OPER={# isnull means is unset
        'INT':['exact','in','gt','gte','lt','lte','range','isnull'],
        'BOOL':['exact','in','isnull'],
        'DATE':['exact','in','gt','gte','lt','lte','range','isnull'],
        'STR':['exact','iexact','contains','icontains','in','gt','gte','lt','lte','range','startswith','endswith','regex','iregex','istartswith','iendswith','isnull'],
        'FLOAT':['exact','in','gt','gte','lt','lte','range','isnull'],
    }
    def __init__(self,tag_name,type,oper,value,is_basic=False):
        self.tag_name=tag_name
        self.value=value #expected a json object
        self.type=type
        self.oper=oper
        self.is_basic=is_basic
        # print("Query ",tag_name," || ",type," ?? ",json.dumps(value))
    def handle(self):
        if self.type not in self.AVAL_TYPE:
            raise RuntimeError("Invalid type")
        if self.oper not in self.AVAL_OPER[self.type]:
            raise RuntimeError(f"Invalid operation for type {self.type}")
        self.qry_val=None
        self.qry_field=None
        if self.is_basic: #basic query
            self.qry_field=self.tag_name
            if self.tag_name=="uploader":
                self.qry_field="uploader__username"
        else:
            self.qry_field=self.FIELD_NAME[self.type]
        if self.type=='INT':
            if self.oper=='range':
                self.qry_val=(int(self.value['min']),int(self.value['max']))
            elif self.oper=='isnull':
                self.qry_val=get_bool(self.value['value'])
            elif self.oper=='in':
                self.qry_val=list(self.value['value'])
            else:
                self.qry_val=int(self.value['value'])
        elif self.type=='BOOL':
            if self.oper=='isnull':
                self.qry_val=get_bool(self.value['value'])
            elif self.oper=='in':
                self.qry_val=list(self.value['value'])
            else:
                self.qry_val=get_bool(self.value['value'])
        elif self.type=='STR':
            if self.oper=='isnull':
                self.qry_val=get_bool(self.value['value'])
            elif self.oper=='in':
                self.qry_val=list(self.value['value'])
            elif self.oper=='range':
                self.qry_val=(str(self.value['min']),str(self.value['max']))
            else:
                self.qry_val=str(self.value['value'])
        elif self.type=='DATE':
            if self.oper=='isnull':
                self.qry_val=get_bool(self.value['value'])
            elif self.oper=='in':
                self.qry_val=[get_date(v) for v in self.value['value']]
            elif self.oper=='range':
                self.qry_val=(get_date(self.value['min']),get_date(self.value['max']))
            else:
                self.qry_val=get_date(self.value['value'])
        elif self.type=='FLOAT':
            if self.oper=='isnull':
                self.qry_val=get_bool(self.value['value'])
            elif self.oper=='in':
                self.qry_val=list(self.value['value'])
            elif self.oper=='range':
                self.qry_val=(float(self.value['min']),float(self.value['max']))
            else:
                self.qry_val=float(self.value['value'])
        lookup=f'{self.qry_field}__{self.oper}'
        qry=Q(**{lookup:self.qry_val})
        if self.is_basic:
            return qry
        ext=Q(tag__name=self.tag_name) & Q(tag__tag_type=self.type)
        safe_q=Q(Exists(ImageTag.objects.filter(ext & qry,image_id=OuterRef('id'))))
        return safe_q


def factor_q(qs):
    qry=None 
    lst_logic="AND"
    for i in qs['query']:
        tag=TagQueryFactory(i['tag'],i['type'],i['oper'],i['value'],get_bool(i['is_basic']))
        qy=tag.handle()
        # print(i)
        if "not" in i:
            if get_bool(i["not"])==True:
                qy=~qy
        if qry is None:
            qry=qy
            if "logic" in i:
                if i["logic"]=='OR':
                    lst_logic='OR'
                else:
                    lst_logic='AND'
            else:
                lst_logic='AND' 
            continue
        if lst_logic=='AND':
            qry=qry&qy
        elif lst_logic=='OR':
            qry=qry|qy
        if "logic" in i:
            if i["logic"]=='OR':
                lst_logic='OR'
            else:
                lst_logic='AND'
        else:
            lst_logic='AND' 
    return qry

@ratelimit(key='ip',rate='10/m',block=False)
def advanced_search(request:HttpRequest):
    if getattr(request,'limited',False):
        response=web_ratelimit_exceed(request)
        if response is not None:
            return response
    if request.method=='POST':
        # handle basic querys
        # try:
        if "query" not in request.POST:
            return HttpResponseBadRequest(content="Argument \'query\' is required.")
        try:
            qs=json.loads(request.POST.get("query"))
            qry=factor_q(qs)
        except Exception as e:
            # print(e)
            return HttpResponseBadRequest(content="Invalid query.")
        if login_check(request,True):
            results=Image.objects.filter(qry).distinct().order_by("-date")
        else:
            results=Image.objects.filter(qry,display=True).distinct().order_by("-date")
        paginator = Paginator(results, 12)
        page_number = qs['page']
        try:
            page_obj = paginator.get_page(page_number)
        except PageNotAnInteger:
            # 如果页码不是整数，返回第一页
            page_obj = paginator.page(1)
        except EmptyPage:
            # 如果页码超出范围，返回最后一页
            page_obj = paginator.page(paginator.num_pages)
        #print("SQL ",str(Image.objects.filter(qry,display=True).distinct().query))
        return render(request, "search_results.html", {'results': page_obj,'paginator': paginator})
    return render(request, 'adv_search.html')



def alter_tag(request:HttpRequest):
    if login_check(request,True)==False:
        return HttpResponseForbidden("Invalid login.")
    if request.method=='GET':
        return HttpResponseBadRequest(content="Bad request type.")
    try:
        data=json.loads(request.body)
        uid=data.get("user")
        cookie=data.get("session_token")
        tid=data.get("id")
        value=data.get("value")
        # print('TAG ',tid,value)
        obj=ImageTag.objects.get(id=tid)
        obj.set_value(value)
        obj.save()
        # print("AFT ",obj.bool_value)
        return HttpResponse("OK")
    except:
        return HttpResponseBadRequest(content="Bad request type.")

@ratelimit(key='ip',rate='60/m',block=False)
def islogin(request:HttpRequest):
    if getattr(request,'limited',False):
        response=api_ratelimit_exceed(request)
        if response is not None:
            return response
    return JsonResponse(data={'ok':login_check(request)})
@ratelimit(key='ip',rate='60/m',block=False)
def isadmin(request:HttpRequest):
    if getattr(request,'limited',False):
        response=api_ratelimit_exceed(request)
        if response is not None:
            return response
    return JsonResponse(data={'ok':login_check(request,True)})


def add_tag(request:HttpRequest):
    if login_check(request,True)==False:
        return HttpResponseForbidden("Invalid login.")
    if request.method=='GET':
        return HttpResponseBadRequest(content="Bad request type.")
    try:
        data=json.loads(request.body)
        # print("req ",data)
        picid=data.get("picture")
        name=data.get("name")
        value=data.get("value")
        tag=Tag.objects.get(name=name)
        pic=Image.objects.get(id=picid)
        obj=ImageTag()
        obj.tag=tag
        obj.image=pic
        obj.set_value(value)
        obj.save()
        return HttpResponse("OK")
    except Exception as e:
        print("Bad request body: ",e)
        return HttpResponseBadRequest(content="Bad request type.")

def del_tag(request:HttpRequest):
    if login_check(request,True)==False:
        return HttpResponseForbidden("Invalid login.")
    if request.method=='GET':
        return HttpResponseBadRequest(content="Bad request type.")
    try:
        data=json.loads(request.body)
        pid=data.get("id")
        tag=ImageTag.objects.get(id=pid)
        tag.delete()
        return HttpResponse("OK")
    except:
        return HttpResponseBadRequest(content="Bad request type.")
    
@require_POST
def switch_status(request:HttpRequest):
    if login_check(request,True)==False:
        return HttpResponseForbidden("Invalid login.")
    data=json.loads(request.body)
    pid=data.get("id")
    image=Image.objects.get(id=pid)
    if image.display==True:
        image.display=False
    else:
        image.display=True
    image.save()
    return HttpResponse("OK")

import random,mimetypes
@ratelimit(key='ip',rate='10/m',block=False)
@never_cache
def random_choice(request:HttpRequest):
    if getattr(request,'limited',False):
        response=api_ratelimit_exceed(request)
        if response is not None:
            return response
    if request.method=='GET':
        if "query" not in request.GET:
            return HttpResponseBadRequest(content="Argument \'query\' is required.")
        try:
            qs=json.loads(request.GET.get("query"))
            qry=factor_q(qs)
        except:
            return HttpResponseBadRequest(content="Invalid query.")
        results=Image.objects.filter(qry,display=True).distinct()
        ids=list(results.values_list('id',flat=True))
        if len(ids)>0:
            rb=Image.objects.get(id=random.choice(ids))
            image=rb.image_file.open('rb')
            ct,_=mimetypes.guess_type(rb.image_file.name)
            if not ct: ct="image/jpeg"
            response=FileResponse(image,content_type=ct)
            response['Cache-Control']='no-store, no-cache, must-revalidate, max-age=0'
            response['Pragma']='no-cache'
            response['Expires']='0'
            return response
        else:
            return HttpResponseNotFound(content="No matching image found.")
    return HttpResponseBadRequest(content="Invalid http action type.")

@ratelimit(key='ip',rate='50/m',block=False)
def random_view(request:HttpRequest):
    if getattr(request,'limited',False):
        response=web_ratelimit_exceed(request)
        if response is not None:
            return response
    return render(request,'random.html')

@ratelimit(key='ip',rate='30/m',block=False)
@require_GET
def get_image_by_id(request:HttpRequest):
    if getattr(request,'limited',False):
        response=api_ratelimit_exceed(request)
        if response is not None:
            return response
    try:
        pid=request.GET.get("id")
        if login_check(request,True):
            rb=Image.objects.get(id=pid)
        else:
            rb=Image.objects.get(id=pid,display=True)
        image=rb.image_file.open('rb')
        ct,_=mimetypes.guess_type(rb.image_file.name)
        if not ct: ct="image/jpeg"
    except:
        return HttpResponseBadRequest(content="Invalid query.")
    response=FileResponse(image,content_type=ct)
    return response

@ratelimit(key='ip',rate='15/m',block=False)
@require_GET
def get_image_count(request:HttpRequest):
    if getattr(request,'limited',False):
        response=api_ratelimit_exceed(request)
        if response is not None:
            return response
    try:
        uid=request.GET.get('id')
    except:
        return HttpResponseBadRequest(content="Invalid query.")
    return JsonResponse(data={'count':Image.objects.filter(uploader__id=uid).count()})

@ratelimit(key='ip',rate='40/m',block=False)
@require_GET
def get_user_id_by_name(request:HttpRequest):
    if getattr(request,'limited',False):
        response=api_ratelimit_exceed(request)
        if response is not None:
            return response
    try:
        uid=request.GET.get('name')
        user=User.objects.get(username=uid)
    except:
        return HttpResponseBadRequest(content="Invalid query.")
    return JsonResponse(data={'id':user.id})

@ratelimit(key='ip',rate='30/m',block=False)
@require_GET
def get_user_info(request:HttpRequest):
    if getattr(request,'limited',False):
        response=api_ratelimit_exceed(request)
        if response is not None:
            return response
    try:
        uid=request.GET.get('id')
        user=User.objects.get(id=uid)
    except:
        return HttpResponseBadRequest(content="Invalid query.")
    return JsonResponse(data={
        'id':user.id,
        'admin':user.is_admin,
        'banned':user.is_banned,
        'description':user.description,
        'name':user.username,
        'join_at':user.created_at.strftime("%Y-%m-%d"),
        'avatar':user.avatar,
        'topimage':user.topimage
    })

import html
@ratelimit(key='ip',rate='10/m',block=False)
@require_POST
def set_user_info(request:HttpRequest):
    if getattr(request,'limited',False):
        response=api_ratelimit_exceed(request)
        if response is not None:
            return response
    if login_check(request)==False:
        return HttpResponseBadRequest(content="Invalid request.")
    uid=request.COOKIES.get("username")
    data=json.loads(request.body)
    typ=data.get("type")
    val=data.get("value")
    # print("Edit ",uid," : ",typ,val)
    user=User.objects.get(id=uid)
    if typ=="description":
        if len(val)>10000:
            return HttpResponseBadRequest('Invalid name.')
        user.description=html.escape(val)
    if typ=="name":
        if len(val)>64:
            return HttpResponseBadRequest('Invalid name.')
        user.username=html.escape(val)
    if typ=="avatar":
        user.avatar=int(val)
    if typ=="topimage":
        user.topimage=int(val)
    user.full_clean()
    user.save()
    return HttpResponse(content="OK")